{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bcbe5c87",
   "metadata": {},
   "source": [
    "# OpenAI Functions\n",
    "\n",
    "These output parsers use OpenAI function calling to structure its outputs. This means they are only usable with models that support function calling. There are a few different variants:\n",
    "\n",
    "- JsonOutputFunctionsParser: Returns the arguments of the function call as JSON\n",
    "- PydanticOutputFunctionsParser: Returns the arguments of the function call as a Pydantic Model\n",
    "- JsonKeyOutputFunctionsParser: Returns the value of specific key in the function call as JSON\n",
    "- PydanticAttrOutputFunctionsParser: Returns the value of specific key in the function call as a Pydantic Model\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "aac4262b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.utils.openai_functions import (\n",
    "    convert_pydantic_to_openai_function,\n",
    ")\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.pydantic_v1 import BaseModel, Field, validator\n",
    "from langchain_openai import ChatOpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "52cb351d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Joke(BaseModel):\n",
    "    \"\"\"Joke to tell user.\"\"\"\n",
    "\n",
    "    setup: str = Field(description=\"question to set up a joke\")\n",
    "    punchline: str = Field(description=\"answer to resolve the joke\")\n",
    "\n",
    "\n",
    "openai_functions = [convert_pydantic_to_openai_function(Joke)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "2c3259c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = ChatOpenAI(temperature=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "d3e9007c",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [(\"system\", \"You are helpful assistant\"), (\"user\", \"{input}\")]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87680951",
   "metadata": {},
   "source": [
    "## JsonOutputFunctionsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cb065bdd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "6ff758c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "parser = JsonOutputFunctionsParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "27a3acd1",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = prompt | model.bind(functions=openai_functions) | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "59b59179",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'setup': \"Why don't scientists trust atoms?\",\n",
       " 'punchline': 'Because they make up everything!'}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "cdbd0a99",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{}\n",
      "{'setup': ''}\n",
      "{'setup': 'Why'}\n",
      "{'setup': 'Why don'}\n",
      "{'setup': \"Why don't\"}\n",
      "{'setup': \"Why don't scientists\"}\n",
      "{'setup': \"Why don't scientists trust\"}\n",
      "{'setup': \"Why don't scientists trust atoms\"}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': ''}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because'}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they'}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make'}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up'}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything'}\n",
      "{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}\n"
     ]
    }
   ],
   "source": [
    "for s in chain.stream({\"input\": \"tell me a joke\"}):\n",
    "    print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ca55ac9",
   "metadata": {},
   "source": [
    "## JsonKeyOutputFunctionsParser\n",
    "\n",
    "This merely extracts a single key from the returned response. This is useful for when you want to return a list of things."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "f8bc404e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "\n",
    "from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "9b91ff36",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Jokes(BaseModel):\n",
    "    \"\"\"Jokes to tell user.\"\"\"\n",
    "\n",
    "    joke: List[Joke]\n",
    "    funniness_level: int"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "c91c5949",
   "metadata": {},
   "outputs": [],
   "source": [
    "parser = JsonKeyOutputFunctionsParser(key_name=\"joke\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "b4583baf",
   "metadata": {},
   "outputs": [],
   "source": [
    "openai_functions = [convert_pydantic_to_openai_function(Jokes)]\n",
    "chain = prompt | model.bind(functions=openai_functions) | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "e8b766ff",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'setup': \"Why don't scientists trust atoms?\",\n",
       "  'punchline': 'Because they make up everything!'},\n",
       " {'setup': 'Why did the scarecrow win an award?',\n",
       "  'punchline': 'Because he was outstanding in his field!'}]"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"input\": \"tell me two jokes\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "f74ef675",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[]\n",
      "[{}]\n",
      "[{'setup': ''}]\n",
      "[{'setup': 'Why'}]\n",
      "[{'setup': 'Why don'}]\n",
      "[{'setup': \"Why don't\"}]\n",
      "[{'setup': \"Why don't scientists\"}]\n",
      "[{'setup': \"Why don't scientists trust\"}]\n",
      "[{'setup': \"Why don't scientists trust atoms\"}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': ''}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': ''}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scare'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': ''}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he was'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he was outstanding'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he was outstanding in'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he was outstanding in his'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he was outstanding in his field'}]\n",
      "[{'setup': \"Why don't scientists trust atoms?\", 'punchline': 'Because they make up everything!'}, {'setup': 'Why did the scarecrow win an award?', 'punchline': 'Because he was outstanding in his field!'}]\n"
     ]
    }
   ],
   "source": [
    "for s in chain.stream({\"input\": \"tell me two jokes\"}):\n",
    "    print(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "941a3d4e",
   "metadata": {},
   "source": [
    "## PydanticOutputFunctionsParser\n",
    "\n",
    "This builds on top of `JsonOutputFunctionsParser` but passes the results to a Pydantic Model. This allows for further validation should you choose."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "f51823fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.output_parsers.openai_functions import PydanticOutputFunctionsParser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "3c6a5e4d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Joke(BaseModel):\n",
    "    \"\"\"Joke to tell user.\"\"\"\n",
    "\n",
    "    setup: str = Field(description=\"question to set up a joke\")\n",
    "    punchline: str = Field(description=\"answer to resolve the joke\")\n",
    "\n",
    "    # You can add custom validation logic easily with Pydantic.\n",
    "    @validator(\"setup\")\n",
    "    def question_ends_with_question_mark(cls, field):\n",
    "        if field[-1] != \"?\":\n",
    "            raise ValueError(\"Badly formed question!\")\n",
    "        return field\n",
    "\n",
    "\n",
    "parser = PydanticOutputFunctionsParser(pydantic_schema=Joke)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "d2bbd54f",
   "metadata": {},
   "outputs": [],
   "source": [
    "openai_functions = [convert_pydantic_to_openai_function(Joke)]\n",
    "chain = prompt | model.bind(functions=openai_functions) | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "db1a06e8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Joke(setup=\"Why don't scientists trust atoms?\", punchline='Because they make up everything!')"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.invoke({\"input\": \"tell me a joke\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d96211e7",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
